package com.zheng.mvcframework.servlet;

import com.zheng.mvcframework.annotations.*;
import com.zheng.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author: zheng
 * @Date: 2020-06-11 17:59
 * @Description:
 */
public class MyServletDispatcher extends HttpServlet {
    //定义配置文件
    private Properties properties = new Properties();
    //扫描包下面的所有的类
    private List<String> classNames = new ArrayList<>();
    //IOC 容器
    private ConcurrentHashMap<String,Object> ioc=new ConcurrentHashMap();
    //存放Handler
    private List<Handler> handlers=new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        //从web.xml 中获取初始化的参数
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");

        //加载springMvc配置文件
        doLoadConfig(contextConfigLocation);
        //扫描包
        doScan(properties.getProperty("scanPackage"));
        //创建IOC容器初始化对象
        doInstance();
        //依赖注入
        doAutowired();
        //构造HandlerMapping将处理器和映射器进行对应
        initHandlerMapping();
        //权限绑定
        doSecurity();


    }

    /**
     *  @author: zheng
     *  @Date: 2020-06-13 16:36
     *  @Description:寻找@Security注解,
     */
    private void doSecurity() {

    }

    /**
     *  @author: zheng
     *  @Date: 2020-06-13 11:01
     *  @Description:
     */
    private void doAutowired() {
        if(ioc.size()==0) return;
        //ioc 有对象则进行依赖注入
        for(Map.Entry<String,Object> entry:ioc.entrySet()){
            //判断对象里面是否有myAutowired 注解
            Field[] fields=entry.getValue().getClass().getDeclaredFields();
            for(Field field:fields){
               if(!field.isAnnotationPresent(MyAutowired.class))   continue;


               //是Autowired 注解
                MyAutowired myAutowired=field.getAnnotation(MyAutowired.class);
                String beanName=myAutowired.value();
                if(StringUtils.isEmpty(beanName))
                    beanName=field.getType().getName();

                field.setAccessible(true);

                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * @author: zheng
     * @Date: 2020-06-13 10:25
     * @Description: 基于反射技术把类实力化
     */
    private void doInstance() {
        if (classNames.size() == 0) return;
        try {
            for (String className : classNames) {
                Class<?> aClass = Class.forName(className);
                //区分类是Controller Service
                if(aClass.isAnnotationPresent(MyController.class)){
                    // controller的id此处不做过多处理，不取value了，就拿类的首字母小写作为id，保存到ioc中
                    String simpleName=aClass.getSimpleName();
                    String lowerFirstSimpleName=lowerFirst(simpleName);
                    Object o=aClass.newInstance();
                    ioc.put(lowerFirstSimpleName,o);
                }else if(aClass.isAnnotationPresent(MyService.class)){
                    //获取MyService 注解上的value
                    MyService myService=aClass.getAnnotation(MyService.class);
                    String value=myService.value();
                    if(!StringUtils.isEmpty(value)){
                     Object o =aClass.newInstance();
                       ioc.put(value,o);
                    }else {
                        String simpleName=aClass.getSimpleName();
                        String lowerFirstSimpleName=lowerFirst(simpleName);
                        Object o=aClass.newInstance();
                        ioc.put(lowerFirstSimpleName,o);
                    }
                    // service层往往是有接口的，面向接口开发，此时再以接口名为id，放入一份对象到ioc中，便于后期根据接口类型注入
                    Class<?> [] interfaces=aClass.getInterfaces();
                    for(Class<?> interfac:interfaces){
                        ioc.put(interfac.getName(),aClass.newInstance());
                    }


                } else{
                     continue;
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     *  @author: zheng
     *  @Date: 2020-06-13 14:33
     *  @Description:构造一个HandlerMapping处理器映射器与url进行映射
     */
    private void initHandlerMapping() {
        if(ioc.size()==0) return;

        for(Map.Entry entry:ioc.entrySet()){
           Class<?> aclass= entry.getValue().getClass();

           if(!aclass.isAnnotationPresent(MyController.class)) continue;

           //获取最上面的RequestMapping
           String bathUrl="";
           if(aclass.isAnnotationPresent(MyRequestMapping.class)){
               MyRequestMapping myRequestMapping=aclass.getAnnotation(MyRequestMapping.class);
               bathUrl=myRequestMapping.value();
           }
           //获取该类的权限
            String classSecourityName="";
           if(aclass.isAnnotationPresent(Security.class)){
               Security security=aclass.getAnnotation(Security.class);
               classSecourityName=security.value();
           }

           //获取Controller 下面所有的所有方法
            Method [] methods=aclass.getMethods();
           for(Method method:methods){
               //寻找controller 下面的所有RequestMapping
               if(!method.isAnnotationPresent(MyRequestMapping.class)) continue;

               MyRequestMapping myRequestMapping=method.getAnnotation(MyRequestMapping.class);
               String url=myRequestMapping.value();
               String methodUrl=bathUrl+url;

               //获取方法上的权限
               String methodSecurityName="";
               if(method.isAnnotationPresent(Security.class)){
                   methodSecurityName=method.getAnnotation(Security.class).value();
               }
               Handler handler=new Handler(entry.getValue(),method, Pattern.compile(methodUrl),classSecourityName,methodSecurityName);


               //计算方法参数信息
               Parameter [] parameters=method.getParameters();

               for(int i=0;i<parameters.length;i++){
                   Parameter parameter=parameters[i];
                   if(parameter.getType()==HttpServletRequest.class||parameter.getType()==HttpServletResponse.class){
                       handler.getParameterIndex().put(parameter.getType().getSimpleName(),i);
                   }else {
                       handler.getParameterIndex().put(parameter.getName(),i);
                   }
               }

               handlers.add(handler);


           }
        }
    }

    /**
     * @param scanPackage
     * @author: zheng
     * @Date: 2020-06-12 16:20
     * @Description:扫描包中的类
     */
    private void doScan(String scanPackage) {
        //获取com/zheng路径
        String scanPath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPath);
        File[] files = pack.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                doScan(scanPackage + "." + file.getName());
            } else {
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }
    }

    private void doLoadConfig(String contextConfigLocation) {
        //把初始化信息加载为流存入到properties 里面
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);

        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("加载异常");
        }
    }

    //首字母小写
    public  String lowerFirst(String className){
        char [] chars=className.toCharArray();
        if('A'<=chars[0]&&chars[0]<='Z'){
            chars[0]+=32;
        }
        return String.valueOf(chars);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setHeader("Content-type", "text/html;charset=UTF-8");

        //根据请求找到对应的Handler
        Handler handler=getHandler(req);

        if(handler==null){
            resp.getWriter().write("404");
            return;
        }




        //获取方法参数
        Class<?> [] paramenters=handler.getMethod().getParameterTypes();
        //创建新的数组
        Object paraValues[]=new Object[paramenters.length];
        //往数组中塞值
        Map<String, String[]> parameterMap = req.getParameterMap();

        // 遍历request中所有参数  （填充除了request，response之外的参数）
        for(Map.Entry<String,String[]> param: parameterMap.entrySet()) {
            // name=1&name=2   name [1,2]
            String value = StringUtils.join(param.getValue(), ",");  // 如同 1,2

            // 如果参数和方法中的参数匹配上了，填充数据
            if(!handler.getParameterIndex().containsKey(param.getKey())) {continue;}

            // 方法形参确实有该参数，找到它的索引位置，对应的把参数值放入paraValues
            Integer index = handler.getParameterIndex().get(param.getKey());//name在第 2 个位置

            paraValues[index] = value;  // 把前台传递过来的参数值填充到对应的位置去

        }


        int requestIndex = handler.getParameterIndex().get(HttpServletRequest.class.getSimpleName()); // 0
        paraValues[requestIndex] = req;


        int responseIndex = handler.getParameterIndex().get(HttpServletResponse.class.getSimpleName()); // 1
        paraValues[responseIndex] = resp;

        //验证权限
        for(Map.Entry<String,Integer> map:handler.getParameterIndex().entrySet()){
                String param= map.getKey();
                if("userName".equals(param)){
                    //获取userName 参数值
                   String userName= paraValues[map.getValue()].toString();
                   //如果Controller 上加入类Security注解,并且设置了权限值,如果权限不相等返回
                   if(!StringUtils.isEmpty(handler.getClassSecurityName())&&!userName.equals(handler.getClassSecurityName())){
                       resp.getWriter().write("该类无权限访问");
                       return;
                   }else if(!StringUtils.isEmpty(handler.getMethodSecurityName())&&!userName.equals(handler.getMethodSecurityName())){
                       //如果方法上设置了Security注解,并设置了访问权限的值,如果权限不相等返回
                       resp.getWriter().write("该方法无权限访问");
                       return;
                   }
                }
        }


        // 最终调用handler的method属性
        try {
            handler.getMethod().invoke(handler.getObject(),paraValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


    }

    private Handler getHandler(HttpServletRequest req) {
        if(handlers.size()==0) return null;
        String url= req.getRequestURI();
        for(Handler handler:handlers){
           Matcher matcher=handler.getPattern().matcher(url);
           if(!matcher.matches()) continue;
           return handler;

        }
        return null;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }
}
